/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author apu
 */
import java.io.*;
import java.util.*;
import java.net.*;

public class GameClient {
    private String URL;
    private Socket connection;
    private Game myGame;
    private PrintWriter out;
    private BufferedReader in;
    private boolean Error;
    private String ErrorString;
    private int state;
    
    public GameClient(String URL){
        connection=null;
        this.URL=URL;   
    }
    
    // Main Protocol... Do not mess with the protocol
    // it may crash the server :(
    private void gameLoop() throws IOException{
        state=0;
        Error=false;
        ErrorString="";
        char cmd;
        Move lastOpponentMove=null;
        while (state!=99){
            //System.out.println("State="+state);
            switch(state){
                case 0:                 // Initiate handshake
                    cmd=getCommand();
                    if (cmd=='n') state=1;
                    else errorState('n',cmd);
                    break;
                case 1:                 // Login
                    if (stringOK(myGame.getUserName()) 
                            && stringOK(myGame.getUserPassword())){
                        send('n');
                        send(myGame.getUserName());
                        send(myGame.getUserPassword());
                        send("P");  // Indicating a player-client connection
                        state=3;
                    }
                    else errorState("ERROR: Invalid username or password!");
                    break;
                // Case 2 is deprecated...
                case 3:                 // Player name exchange
                    cmd=getCommand();
                    if (cmd!='l') errorState('l',cmd);
                    else {
                        ArrayList<String> players=getList();
                        ArrayList<String> pref=myGame.preferenceInit(players);
                        if (listOK(pref)){
                            send('p');
                            send(pref);
                            state=4;
                        }
                        else errorState("ERROR: Invalid character in "+
                                "Preference list!");
                    }
                    break;
                case 4:             // Handshake complete
                    // Checkpoint/handshake complete
                    cmd=getCommand();
                    if (cmd!='o') errorState('o',cmd);
                    else state=5;
                    break;
                case 5:             // getting a game request
                    cmd=getCommand();
                    if (cmd=='o') {
                        send('o');
                    }
                    else if (cmd!='g') errorState('g',cmd);
                    else{
                        //int gameID=Integer.parseInt(getString());
                        String opponent=getString();
                        int opponentID=Integer.parseInt(getString());
                        if (myGame.Initialize_New_Game(opponent, opponentID)){
                            send('o');
                            lastOpponentMove=null;
                            state=6;  
                        }
                        else send('c'); //reject challenge
                    }
                    break;
                case 6:                 // Starting a new game
                    cmd=getCommand();   // Or Get declined
                    if (cmd=='s'){
                        String gids=getString();
                        String sym=getString();
                        int gid;
                        char symbol;
                        gid=Integer.parseInt(gids);
                        if (sym.length()!=1) errorState("Invalid symbol");
                        else{
                            symbol=sym.charAt(0);
                            myGame.setSymbol(symbol);
                            myGame.Receive_New_Game_ID(gid, symbol);
                            if (symbol=='x') state=7;
                            else if (symbol=='o') state=8;
                            else errorState("Invalid player symbol received: "
                                +symbol);
                        }
                    }
                    else if (cmd=='c'){     // Game is declined by other party
                        myGame.Game_Declined();
                        state=5;
                    }
                    else errorState('s',cmd);
                    break;
                case 7:     // Players Move!
                    myGame.Set_Active(false);
                    Move myMove=myGame.Update_Game(lastOpponentMove);
                    if (myMove==null) errorState("ERROR: NULL MOVE BY MYSELF!");
                    else if (!myMove.isBound()) 
                        errorState("ERROR: I chose an invalid move ["+
                                myMove.Row()+", "+myMove.Col()+"]");
                    else{
                        send('m');
                        send(myMove.Row());
                        send(myMove.Col());
                        state=9;
                    }
                    myGame.Set_Active(true);
                    break;
                case 8:
                    myGame.Set_Active(true);    // Running a thread while
                    cmd=getCommand();           // waiting for opponent
                    if (cmd=='m') {
                        lastOpponentMove=new Move(getInt(),getInt());
                        if (!lastOpponentMove.isBound()) 
                            errorState("ERROR: Server responded with invalid "
                                    + "move: ["+lastOpponentMove.Row()+
                                    ", "+lastOpponentMove.Col()+"]");
                        else state=7;
                    }
                    else if (cmd=='w'){
                        int thewinner=getInt();
                        lastOpponentMove=new Move(getInt(),getInt());
                        if ((lastOpponentMove.Row()==64 &&
                                 lastOpponentMove.Col()==64))
                            myGame.gameOver(thewinner, null);
                        else myGame.gameOver(thewinner, lastOpponentMove);
                        state=5;
                    }
                    else errorState("UNKNOWN");
                    break;
                case 9:
                    cmd=getCommand();
                    if (cmd=='o') state=8;
                    else if (cmd=='w'){
                        int thewinner=getInt();
                        lastOpponentMove=new Move(getInt(),getInt());                        
                        myGame.gameOver(thewinner, null);
                        state=5;
                    }
                    else errorState("UNKNOWN");
                    break;
                    
                         
                default: state=99;break;
            }
        }
        
        if (Error){
            if (ErrorString.equals("")) ErrorString="UNKNOWN";
            System.err.println("\n\nERROR: "+ErrorString);
        }
        System.out.println("\n\nGame Session Terminated\n\n");
        //connection.close();
    }
    
    public void execute() throws IOException{
        myGame=new Game();
        myGame.start();
        
        
        try {
            connection = new Socket(URL, 2000);
            out = new PrintWriter(connection.getOutputStream());
            in = new BufferedReader(new InputStreamReader(
                                        connection.getInputStream()));
        } catch (UnknownHostException e) {
            System.err.println("Unable to Connect to host: "+URL);
            connection.close();
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Unable to Bind I/O for "
                               + "the connection to: " +URL);
            connection.close();
            System.exit(1);
        }
        
        try {
        
            System.out.println("Connection Established with server!\n");
            gameLoop();
            in.close();
            out.close();
            connection.close();
        }
        catch (Exception e){
            System.err.println("FATAL ERROR: Connection disrupted with Server."
                    +" Program Aborted");
            System.err.println(e.toString());
            in.close();
            out.close();
            connection.close();
            System.exit(1);  
        }
    }
    private String getString() throws IOException{
        String s="";
        char c=(char)in.read();
        while (c!='#'){
            s=s+c;
            c=(char)in.read();
        }
        return s;
    }
    private ArrayList<String> getList() throws IOException{
        String s;
        ArrayList<String> list=new ArrayList<String>();
        list.clear();
        s=getString();
        while(!s.isEmpty()){
            list.add(s);
            s=getString();
        }
        return list;
    }
    private char getChar() throws IOException{
        return Character.toLowerCase((char)in.read());
    }
    private char getCommand() throws IOException{
        char c=Character.toLowerCase((char)in.read());
        switch (c){
            case 'w':
            case 'o':
            case 'c':
            case'n':
            case 'l':
            case 'p':
            case 'g':
            case 's':
            case 'm':
                break;
            case 'e':
                errorState(getString());
                break;
            default:
                errorState("Invalid command received: "+c);      
        }
        return c;
    }
    private int getInt() throws IOException{
        return in.read();
    }
    private void send(String s) throws IOException{
        out.write(s+"#");
        out.flush();
    }
    private void send(char c) throws IOException{
        out.write(Character.toLowerCase(c));
        out.flush();
    }
    private void send(int x) throws IOException{
        out.write((char)(x&0xFF));
        out.flush();
    }
    private void send(ArrayList<String> list) throws IOException{
        for (int i=0;i<list.size();i++)
            send(list.get(i)+"#");
        send('#');
    }
    private void errorState(String msg){
        if (!Error){
            Error=true;
            state=99;
            ErrorString=msg;
        }
    }
    private void errorState(char expected, char cmd){
        errorState("Invalid response: Expected " + expected+", got "+cmd);
    }
    private boolean stringOK(String s){
        s=s.toLowerCase();
        if (s.length()>15) return false;
        for (int i=0;i<s.length();i++) {
            char c=s.charAt(i);
            if (!((c>='a' && c<='z') || (c>='0' && c<='9')
                    || c=='_')) return false;
        }
        return true;
    }
    private boolean listOK(ArrayList<String> list){
        if (list.size()>10) return false;
        for (int i=0;i<list.size();i++) if (!stringOK(list.get(i))) 
            return false;
        return true;
    }
        
}
